﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace demoReflection
{
    
    public class CustomPropertyInfo {
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        private object _value;

        public object Value
        {
            get { return _value; }
            set { _value = value; }
        }

        private PropertyInfo _propertyInfo;

        public PropertyInfo PropertyInfo
        {
            get { return _propertyInfo; }
            set { _propertyInfo = value; }
        }
    }
    public class CustomAttributeInfo {

        public CustomAttributeInfo() {
            _constructorInfos = new List<CustomPropertyInfo>();
            _propertiesInfos = new List<CustomPropertyInfo>();

        }

        private string _attributeName;

        public string AttributeName
        {
            get { return _attributeName; }
            set { _attributeName = value; }
        }

        private List<CustomPropertyInfo> _constructorInfos;

        public List<CustomPropertyInfo> ConstructorInfos
        {
            get { return _constructorInfos; }
            set { _constructorInfos = value; }
        }

        private List<CustomPropertyInfo> _propertiesInfos;

        public List<CustomPropertyInfo> PropertiesInfos
        {
            get { return _propertiesInfos; }
            set { _propertiesInfos = value; }
        }
    }

    public class CustomFieldInfo
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        private string _primaryType;

        public string PrimaryType
        {
            get { return _primaryType; }
            set { _primaryType = value; }
        }

        private string[] _secondaryTypes;

        public string[] SecondaryTypes
        {
            get { return _secondaryTypes; }
            set { _secondaryTypes = value; }
        }

        private List<CustomAttributeInfo> _Attributes;
        public List<CustomAttributeInfo> Attributes
        {
            get { return _Attributes; }
            set { _Attributes = value; }
        }

        private bool _isVirtualProperty;

        public bool IsVirtualProperty
        {
            get { return _isVirtualProperty; }
            set { _isVirtualProperty = value; }
        }

        public CustomFieldInfo()
        {
            _isVirtualProperty = false;
        }
    }

    public class AutoGenerateType 
    {
        
        private string _moduleName;

        public string ModuleName
        {
            get { return _moduleName; }
            set { _moduleName = value; }
        }
        private string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        // name parent type
        private string _parent;

        public string Parent
        {
            get { return _parent; }
            set { _parent = value; }
        }

        private List<CustomFieldInfo> _fields;
        public List<CustomFieldInfo> Fields
        {
            get { return _fields; }
            set { _fields = value; }
        }

        private List<CustomAttributeInfo> _attributes;

        public List<CustomAttributeInfo> Attributes
        {
            get { return _attributes; }
            set { _attributes = value; }
        }


        public AutoGenerateType(string typeName) {
            _name = typeName;
            _fields = new List<CustomFieldInfo>();
            _attributes = new List<CustomAttributeInfo>();
        }

        public AutoGenerateType(string Name, List<CustomFieldInfo> fields, List<CustomAttributeInfo> attributes = null) {
            _name = Name;
            _fields = fields;
            _attributes = attributes;
        }

        public void Build(string moduleName,TypeBuilder typeBuilder, List<TypeBuilder> customTypeBuilders)
        {
            
            if (!string.IsNullOrWhiteSpace(_parent))
            {
                Type typeParent = Helper.TryGetType(_parent);
                if (typeParent == null)
                    typeParent = customTypeBuilders.FirstOrDefault(t => t.FullName == _parent);
                if (typeParent != null)
                {
                    typeBuilder.SetParent(typeParent);
                }
            }
            // create attributes for type
            List<Type> parameterTypes = new List<Type>();
            List<FieldBuilder> fieldBuilders = new List<FieldBuilder>();
            List<PropertyBuilder> propertyBuilders = new List<PropertyBuilder>();
            if (_attributes != null)
            {
                foreach (var infoAttr in _attributes)
                {
                    
                    Type attr = Helper.TryGetType(infoAttr.AttributeName);
                    if (attr != null)
                    {
                        ConstructorInfo cons = attr.GetConstructor(new Type[] { });
                        if (cons != null)
                        {
                            List<CustomPropertyInfo> propertyAttrInfos = infoAttr.PropertiesInfos;
                            if (infoAttr.PropertiesInfos != null)
                                GetPropertyInfos(ref propertyAttrInfos, attr);

                            if (propertyAttrInfos != null && propertyAttrInfos.Count > 0)
                            {
                                List<PropertyInfo> propInfors = new List<PropertyInfo>();
                                List<object> propValues = new List<object>();
                                for (int i = 0; i < propertyAttrInfos.Count; i++)
                                {
                                    propInfors.Add(propertyAttrInfos[i].PropertyInfo);
                                    propValues.Add(propertyAttrInfos[i].Value);
                                }
                                typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(cons, new object[] { }, propInfors.ToArray(), propValues.ToArray()));
                            }
                            else
                                typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(cons, new object[] { }));
                        }
                    }
                }
                
            }

            // create fields & properties
            if (_fields != null)
            {
                foreach (var field in _fields)
                {
                    
                    MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
                    if (field.IsVirtualProperty)
                        getSetAttr |= MethodAttributes.Virtual;

                    // Find type of field
                    Type typeField = Helper.TryGetType(field.PrimaryType);
                    if (typeField == null)
                    {
                        typeField = customTypeBuilders.FirstOrDefault(t => t.FullName == field.PrimaryType);
                    }
                    if (typeField != null)
                    {
                        if (typeField.IsGenericType)
                        {
                            List<Type> typeAgrs = Helper.TryGetTypes(field.SecondaryTypes);
                            if (typeAgrs.Count == 0)
                            {
                                for (int i = 0; i < field.SecondaryTypes.Length; i++)
                                {
                                    Type type = customTypeBuilders.FirstOrDefault(t => t.FullName.Equals(field.SecondaryTypes[i]));
                                    if (type != null)
                                        typeAgrs.Add(type);
                                }
                                //typeAgrs = new Type[] { typeof(object) };
                            }
                            typeField = typeField.MakeGenericType(typeAgrs.ToArray());

                        }
                        // define field
                        parameterTypes.Add(typeField);
                        FieldBuilder fbConcrete = typeBuilder.DefineField("_" + field.Name, typeField, FieldAttributes.Private);

                        //--create property
                        string propName = FirstCharToUpper(field.Name); 
                        PropertyBuilder pbConcrete = typeBuilder.DefineProperty(propName, PropertyAttributes.HasDefault, typeField, null);
                        MethodBuilder mbGetAccessor = typeBuilder.DefineMethod("get" + propName, getSetAttr, typeField, Type.EmptyTypes);
                        ILGenerator fieldGetIL = mbGetAccessor.GetILGenerator();

                        // For an instance property, argument zero is the instance. Load the  
                        // instance, then load the private field and return, leaving the 
                        // field value on the stack.
                        fieldGetIL.Emit(OpCodes.Ldarg_0);
                        fieldGetIL.Emit(OpCodes.Ldfld, fbConcrete);
                        fieldGetIL.Emit(OpCodes.Ret);

                        MethodBuilder mbSetAccessor = typeBuilder.DefineMethod("set" + propName, getSetAttr, null, new Type[] { typeField });
                        ILGenerator fieldSetIL = mbSetAccessor.GetILGenerator();
                        // Load the instance and then the numeric argument, then store the 
                        // argument in the field.
                        fieldSetIL.Emit(OpCodes.Ldarg_0);
                        fieldSetIL.Emit(OpCodes.Ldarg_1);
                        fieldSetIL.Emit(OpCodes.Stfld, fbConcrete);
                        fieldSetIL.Emit(OpCodes.Ret);

                        pbConcrete.SetGetMethod(mbGetAccessor);
                        pbConcrete.SetSetMethod(mbSetAccessor);
                        
                        // add attributes for property
                        if (field.Attributes != null)
                        {
                            foreach (var infoAttr in field.Attributes)
                            {
                                Type attr = Helper.TryGetType(infoAttr.AttributeName);
                                if (attr != null)
                                {
                                    Type[] typeConstructors = null;
                                    object[] objectConstructors = null;
                                    // prepare type, value of attribute constructor
                                    if (infoAttr.ConstructorInfos != null)
                                    {
                                        List<string> nameTypeConstructors = new List<string>();
                                        List<object> valueObjectConstructors = new List<object>();
                                        for (int i = 0; i < infoAttr.ConstructorInfos.Count; i++)
                                        {
                                            nameTypeConstructors.Add(infoAttr.ConstructorInfos[i].Name);
                                            valueObjectConstructors.Add(infoAttr.ConstructorInfos[i].Value);
                                        }
                                        typeConstructors = Helper.TryGetTypes(nameTypeConstructors.ToArray()).ToArray();
                                        objectConstructors = valueObjectConstructors.ToArray();
                                    }
                                    else
                                    {
                                        typeConstructors = new Type[] { };
                                        objectConstructors = new object[] { };
                                    }

                                    ConstructorInfo cons = attr.GetConstructor(typeConstructors);
                                    if (cons != null)
                                    {
                                        List<CustomPropertyInfo> propertyAttrInfos = infoAttr.PropertiesInfos;
                                        if (infoAttr.PropertiesInfos != null)
                                            GetPropertyInfos(ref propertyAttrInfos, attr);

                                        if (propertyAttrInfos != null && propertyAttrInfos.Count > 0)
                                        {
                                            List<PropertyInfo> propInfors = new List<PropertyInfo>();
                                            List<object> propValues = new List<object>();
                                            for (int i = 0; i < propertyAttrInfos.Count; i++)
                                            {
                                                propInfors.Add(propertyAttrInfos[i].PropertyInfo);
                                                propValues.Add(propertyAttrInfos[i].Value);
                                            }
                                            pbConcrete.SetCustomAttribute(new CustomAttributeBuilder(cons, objectConstructors, propInfors.ToArray(), propValues.ToArray()));
                                        }
                                        else
                                            pbConcrete.SetCustomAttribute(new CustomAttributeBuilder(cons, objectConstructors));
                                    }

                                }
                            }
                        }

                        // add field to list
                        fieldBuilders.Add(fbConcrete);
                        // add property to list
                        propertyBuilders.Add(pbConcrete);
                    }
                }
            }
            
            //ConstructorBuilder ctor1 = tb.DefineConstructor(
            //    MethodAttributes.Public,
            //    CallingConventions.Standard,
            //    parameterTypes.ToArray());
           

            //ILGenerator ctor1IL = ctor1.GetILGenerator();
            //// For a constructor, argument zero is a reference to the new 
            //// instance. Push it on the stack before calling the base 
            //// class constructor. Specify the default constructor of the  
            //// base class (System.Object) by passing an empty array of  
            //// types (Type.EmptyTypes) to GetConstructor.
            //ctor1IL.Emit(OpCodes.Ldarg_0);
            //ctor1IL.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
            //// Push the instance on the stack before pushing the argument 
            //// that is to be assigned to the private field m_number.
            //ctor1IL.Emit(OpCodes.Ldarg_0);
            ////ctor1IL.Emit(OpCodes.Ldarg_1);
            //for (int i = 0; i < fieldBuilders.Count; ++i)
            //{
            //    ctor1IL.Emit(OpCodes.Ldarg, i+1);
            //    ctor1IL.Emit(OpCodes.Stfld, fieldBuilders[i]);
            //}
            
            //ctor1IL.Emit(OpCodes.Ret);

            //// Define a default constructor that supplies a default value 
            //// for the private field. For parameter types, pass the empty 
            //// array of types or pass null.
            //ConstructorBuilder ctor0 = tb.DefineConstructor(
            //    MethodAttributes.Public,
            //    CallingConventions.Standard,
            //    Type.EmptyTypes);

            //ILGenerator ctor0IL = ctor0.GetILGenerator();
            //// For a constructor, argument zero is a reference to the new 
            //// instance. Push it on the stack before pushing the default 
            //// value on the stack, then call constructor ctor1.
            //ctor0IL.Emit(OpCodes.Ldarg_0);
            //ctor0IL.Emit(OpCodes.Ldc_I4_S, 42);
            //ctor0IL.Emit(OpCodes.Call, ctor1);
            //ctor0IL.Emit(OpCodes.Ret);

            //tb.CreateType();

        }

        private string FirstCharToUpper(string input)
        {
            if (String.IsNullOrEmpty(input))
                return "";
            return input.First().ToString().ToUpper() + String.Join("", input.Skip(1));
        }

        private void GetPropertyInfos(ref List<CustomPropertyInfo> propertyAttrInfos, Type attribute)
        {
            for (int i = (propertyAttrInfos.Count-1) ; i>=0; i--)
            {
                PropertyInfo propertyInfo = attribute.GetProperty(propertyAttrInfos[i].Name);
                if (propertyInfo != null)
                    propertyAttrInfos[i].PropertyInfo = propertyInfo;
                else
                {
                    propertyAttrInfos.RemoveAt(i);
                }
            }
        }
    }


}
